﻿using System;
using Moq;
using NUnit.Framework;
using Objectflow.core.tests.TestOperations;
using Rainbow.ObjectFlow.Engine;
using Rainbow.ObjectFlow.Framework;
using Rainbow.ObjectFlow.Helpers;
using Rainbow.ObjectFlow.Interfaces;
using Shouldly;

namespace Objectflow.core.tests.ComposeSequentialWorkflow
{
    // parser, interpreter, commands, engine
    public class WhenDefiningWorkflow 
    {
        public class WorkflowTests 
        {
            [Test]
            public void Should_add_task_to_workflow_builder()
            {
                var builder = new Mock<SequentialBuilder<string>>();
                var workflowEngine = new Workflow<string>(builder.Object);

                var task = new Func<string, string>((x) => x + "a");
                workflowEngine.Do(task);

                builder.Verify(b => b.AddOperation(task));
            }

            [Test]
            public void Should_add_workflow_to_builder()
            {
                var builder = new Mock<SequentialBuilder<string>>();
                var workflowEngine = new Workflow<string>(builder.Object);

                var task = new Workflow<string>(); 
                workflowEngine.Do(task);

                builder.Verify(b => b.AddOperation(task));
            }

            [Test]
            public void Should_add_AsAWorkflow_to_builder()
            {
                var builder = new Mock<SequentialBuilder<string>>();
                var workflowEngine = new Workflow<string>(builder.Object);

                var task = new Mock<AsAWorkflow<string>>().As<IExecuteWorkflow<string>>();

                workflowEngine.Do(task.Object);

                builder.Verify(b => b.AddOperation(task.Object));
            }
        }

        public class WorkflowBuilder 
        {
            [Test]
            public void Should_not_send_notifications_if_no_subscribers()
            {
                var builder = new SequentialBuilder<string>();
                builder.AddOperation(x => "");
            }

            [Test]
            public void notifies_listener_on_AddOperation()
            {
                var builder = new SequentialBuilder<string>();
                var listener = new Mock<ICommandListener<string>>();

                builder.OperationAdded += listener.Object.OperationAdded;

                builder.AddOperation(x => "");

                listener.Verify(l => l.OperationAdded(It.IsAny<OperationDuplex<string>>()), Times.Exactly(1));
            }

            [Test]
            public void AddOperation_adds_task()
            {
                var builder = new SequentialBuilder<string>();
                var tasklist = new TaskList<string>();
                builder.OperationAdded += tasklist.OperationAdded;

                builder.AddOperation(x => "");
                tasklist.Count.ShouldBe(1);
            }
        }

        private IOperation<Colour> _doubleSpace;
        private Workflow<Colour> _workflow;
        private SequentialBuilder<Colour> _sequentialBuilder;

        [SetUp]
        public void Given()
        {
            _sequentialBuilder = new SequentialBuilder<Colour>();
            _workflow = new Workflow<Colour>(_sequentialBuilder);
            _doubleSpace = new DoubleSpace();
        }

        [Test]
        public void ShouldCheckForNullOperation()
        {
            const BasicOperation<Colour> operation = null;
            Assert.Throws<ArgumentNullException>(() => _workflow.Do(operation), "thrown exception");
        }

        [Test]
        public void ShouldCheckForNullConstraint()
        {
            Exception exception = Assert.Throws<ArgumentNullException>(() => _workflow.Do(_doubleSpace, null));

            Assert.That(exception.Message, Is.StringContaining("Argument [constraint] cannot be null"));
        }

        [Test]
        public void ShouldCheckForNullOperationWithConstraint()
        {
            const IOperation<Colour> operation = null;
            Exception exception = Assert.Throws<ArgumentNullException>(() => _workflow.Do(operation, If.IsTrue(true)));

            Assert.That(exception.Message, Is.StringContaining("Argument [operation] cannot be null"));
        }

        [Test]
        public void ShouldCheckForNullOperationBeforeNullConstraint()
        {
            const IOperation<Colour> operation = null;
            Exception exception = Assert.Throws<ArgumentNullException>(() => _workflow.Do(operation, null));

            Assert.That(exception.Message, Is.StringContaining("Argument [operation] cannot be null"));
        }

        [Test]
        public void ShouldCheckOperationIsInstanceOfOperationTemplate()
        {
            var operation = new Mock<IOperation<Colour>>();
            Assert.Throws<InvalidCastException>(() => Workflow<Colour>.Definition().Do(operation.Object), "Exception");
        }

        [Test]
        public void ShouldCheckOperationInstanceWithConstraints()
        {
            var operation = new Mock<IOperation<Colour>>();
            Assert.Throws<InvalidCastException>(() => Workflow<Colour>.Definition().Do(operation.Object, If.IsTrue(true)), "Exception");
        }
    }
}